'abuprofiler.monkey by Belimoth

'TODO
'		stop managing active list after a while?
'		make FRAMES_TO_PROFILE a Global and add to the documentation?

Strict
Import mojo
Import abutoolbox



Public

#If CONFIG = "debug"

Class Profiler

Public
	Const FRAMES_TO_PROFILE:Int = 60 * 5				'5 seconds at 60 FPS
	
	Function Update:Void()
		For Local profiler:Profiler = EachIn profilers.Values()
			profiler.results[tracker] = profiler.timeConsumed
			profiler.timeConsumed = 0
		Next
		tracker = (tracker + 1) Mod FRAMES_TO_PROFILE
	End
	
	Function Start:Void(name:String, nest:Bool = True, rgb:Int[] = [255, 255, 255])
		Local profiler:Profiler = profilers.Get(name)
		If profiler = Null
			Local parent:Profiler
			If nest = True
				If Not activeProfilers.IsEmpty()
					parent = activeProfilers.Last()
				EndIf
			EndIf
			profiler = New Profiler(name, rgb, parent)
		EndIf
		profiler._Start()
	End
	
	Function Start:Void(name:String, rgb:Int[])
		Start(name, True, rgb)
	End
	
	Function Stop:Void(name:String = "this string isn't really necessary, it will just stop the most recent profiler to preserve nesting")
		If Not activeProfilers.IsEmpty()
			activeProfilers.Last()._Stop()
		Else
			Error "Profiler nesting error."
		Endif
	End
	
	Function Mark:Void(label:String)
		'TODO
	End
	
	Function Draw:Void(x:Int, y:Int)
		_Render()
		SetColor(255, 255, 255)
		DrawImage _image, x, y
	End
	
Private
	Global _image:Image
	Global _pixels:Int[]
	Global _rgb:Int
	
	Global profilers := New StringMap<Profiler>
	Global activeProfilers := New List<Profiler>
		Field link:list.Node<Profiler>
	Global tracker:Int
	
	Field name:String
	Field rgb:Int[]
	Field parent:Profiler
	Field children := New StringMap<Profiler>
	
	Field timeStart:Int
	Field timeConsumed:Int
	Field calls:Int
	Field total:Int, totalCalls:Int
	Field results:Int[]
	
	Method New(name:String, rgb:Int[], parent:Profiler)
		Self.name = name
		Self.rgb = rgb
		Self.parent = parent
		If parent <> Null
			parent.children.Add(name, Self)
		Endif
		Self.results = New Int[FRAMES_TO_PROFILE]
		profilers.Add(name, Self)
	End
	
	Method _Start:Void()
		link = activeProfilers.AddLast(Self)
		timeStart = Millisecs()
	End
	
	Method _Stop:Void()
		Local timePassed:Int = Millisecs() - timeStart
		timeConsumed += timePassed
		calls += 1
		total += timePassed
		totalCalls += 1
		link.Remove()
	End
	
	Function _Render:Void()
		If _image = Null
			_image = CreateImage(FRAMES_TO_PROFILE, 40)
		EndIf
		
		_pixels = FillIntArray(_image.Width() * _image.Height(), $ff000000)
		For Local n := 0 Until FRAMES_TO_PROFILE
			Local x:Int = FRAMES_TO_PROFILE - 1 - (FRAMES_TO_PROFILE + tracker - n - 1) Mod FRAMES_TO_PROFILE				'what
			x = x
			Local y:Int = 39
			For Local profiler:Profiler = EachIn profilers.Values()
				If profiler.parent = Null
					If profiler.results[n] > 0
						_rgb = ($ff000000) | (profiler.rgb[0] Shl 16) | (profiler.rgb[1] Shl 8) | profiler.rgb[2]
						Local yNew:Int = y - profiler.results[n]
						_DrawColumn(x, y, yNew)
						y = yNew - 1
					EndIf
				EndIf
			Next
		Next
		
		_image.WritePixels(_pixels, 0, 0, _image.Width(), _image.Height())
	End
	
	Function _DrawColumn:Void(x:Int, y0:Int, y1:Int)
		y1 = Max(0, y1)
		For Local y:Int = y1 To y0
			_DrawPoint(x, y)
		Next
	End
	
	Function _DrawPoint:Void(x:Int, y:Int)
		_pixels[x + y * _image.Width()] = _rgb
	End
End

#Else

Class Profiler
	Const FRAMES_TO_PROFILE:Int = 60 * 5				'5 seconds at 60 FPS
	Function Update:Void() End
	Function Start:Void(name:String, nest:Bool = True, rgb:Int[] = [255, 255, 255]) End			'TODO, not nesting doesn't really work with the current Stop() implementation
	Function Start:Void(name:String, rgb:Int[]) End
	Function Stop:Void(name:String = "this string isn't really necessary, it will just stop the most recent profiler to preserve nesting") End
	Function Mark:Void(label:String) End
	Function Draw:Void(x:Int, y:Int) End
End

#End



Function UpdateProfiler:Void()
	Profiler.Update()
End